#ifndef XRPL_BASICS_RANGESET_H_INCLUDED
#define XRPL_BASICS_RANGESET_H_INCLUDED

#include <xrpl/beast/core/LexicalCast.h>

#include <boost/algorithm/string.hpp>
#include <boost/icl/closed_interval.hpp>
#include <boost/icl/interval_set.hpp>

#include <optional>
#include <string>
#include <vector>

namespace ripple {

/** A closed interval over the domain T.

    For an instance ClosedInterval c, this represents the closed interval
    (c.first(), c.last()).  A single element interval has c.first() == c.last().

    This is simply a type-alias for boost interval container library interval
    set, so users should consult that documentation for available supporting
    member and free functions.
*/
template <class T>
using ClosedInterval = boost::icl::closed_interval<T>;

/** Create a closed range interval

    Helper function to create a closed range interval without having to qualify
    the template argument.
*/
template <class T>
ClosedInterval<T>
range(T low, T high)
{
    return ClosedInterval<T>(low, high);
}

/** A set of closed intervals over the domain T.

    Represents a set of values of the domain T using the minimum number
    of disjoint ClosedInterval<T>.  This is useful to represent ranges of
    T where a few instances are missing, e.g. the set 1-5,8-9,11-14.

    This is simply a type-alias for boost interval container library interval
    set, so users should consult that documentation for available supporting
    member and free functions.
*/
template <class T>
using RangeSet = boost::icl::interval_set<T, std::less, ClosedInterval<T>>;

/** Convert a ClosedInterval to a styled string

    The styled string is
        "c.first()-c.last()"  if c.first() != c.last()
        "c.first()" if c.first() == c.last()

    @param ci The closed interval to convert
    @return The style string
*/
template <class T>
std::string
to_string(ClosedInterval<T> const& ci)
{
    if (ci.first() == ci.last())
        return std::to_string(ci.first());
    return std::to_string(ci.first()) + "-" + std::to_string(ci.last());
}

/** Convert the given RangeSet to a styled string.

    The styled string representation is the set of disjoint intervals joined
    by commas.  The string "empty" is returned if the set is empty.

    @param rs The rangeset to convert
    @return The styled string
*/
template <class T>
std::string
to_string(RangeSet<T> const& rs)
{
    if (rs.empty())
        return "empty";

    std::string s;
    for (auto const& interval : rs)
        s += ripple::to_string(interval) + ",";
    s.pop_back();

    return s;
}

/** Convert the given styled string to a RangeSet.

    The styled string representation is the set
    of disjoint intervals joined by commas.

    @param rs The set to be populated
    @param s The styled string to convert
    @return True on successfully converting styled string
*/
template <class T>
[[nodiscard]] bool
from_string(RangeSet<T>& rs, std::string const& s)
{
    std::vector<std::string> intervals;
    std::vector<std::string> tokens;
    bool result{true};

    rs.clear();
    boost::split(tokens, s, boost::algorithm::is_any_of(","));
    for (auto const& t : tokens)
    {
        boost::split(intervals, t, boost::algorithm::is_any_of("-"));
        switch (intervals.size())
        {
            case 1: {
                T front;
                if (!beast::lexicalCastChecked(front, intervals.front()))
                    result = false;
                else
                    rs.insert(front);
                break;
            }
            case 2: {
                T front;
                if (!beast::lexicalCastChecked(front, intervals.front()))
                    result = false;
                else
                {
                    T back;
                    if (!beast::lexicalCastChecked(back, intervals.back()))
                        result = false;
                    else
                        rs.insert(range(front, back));
                }
                break;
            }
            default:
                result = false;
        }

        if (!result)
            break;
        intervals.clear();
    }

    if (!result)
        rs.clear();
    return result;
}

/** Find the largest value not in the set that is less than a given value.

    @param rs The set of interest
    @param t The value that must be larger than the result
    @param minVal (Default is 0) The smallest allowed value
    @return The largest v such that minV <= v < t and !contains(rs, v) or
            std::nullopt if no such v exists.
*/
template <class T>
std::optional<T>
prevMissing(RangeSet<T> const& rs, T t, T minVal = 0)
{
    if (rs.empty() || t == minVal)
        return std::nullopt;
    RangeSet<T> tgt{ClosedInterval<T>{minVal, t - 1}};
    tgt -= rs;
    if (tgt.empty())
        return std::nullopt;
    return boost::icl::last(tgt);
}

}  // namespace ripple

#endif
